
namespace Ex
{
    using System.Collections;
    using KeraLua;
    public class ExRefTable : IExETable
    {
        protected readonly EnvLua env;

        public EnvLua CurrentLua
        {
            get { return env; }
        }

        private int? ref_id;

        protected int RefId
        {
            get
            {
                if (!ref_id.HasValue)
                {
                    throw new Exception("unexpected error table has been destroyed");
                }
                return ref_id.Value;
            }
        }

        private ExRefTable(EnvLua env, int ref_id)
        {
            this.env = env;
            this.ref_id = ref_id;
        }

        public ExRefTable(EnvLua env, IDictionary? table = null)
        {
            this.env = env;
            var lua = env.Current;
            lock (env)
            {
                lua.NewTable();
                if (table != null)
                {
                    foreach (var key in table.Keys)
                    {
                        env.PushObjectToLuaStack(key);
                        env.PushObjectToLuaStack(table[key] ?? UsableLuaType.nil);
                        lua.SetTable(-3);
                    }
                }
                ref_id = lua.Ref(LuaRegistry.Index);
            }
        }

        public ExRefTable(EnvLua env, IDictionary<string, object> str_table)
        {
            this.env = env;
            var lua = env.Current;
            lock (env)
            {
                lua.NewTable();
                if (str_table != null)
                {
                    foreach (var key in str_table.Keys)
                    {
                        env.PushObjectToLuaStack(key);
                        env.PushObjectToLuaStack(str_table[key] ?? UsableLuaType.nil);
                        lua.SetTable(-3);
                    }
                }
                ref_id = lua.Ref(LuaRegistry.Index);
            }
        }

        public ExRefTable(EnvLua env, IList arr)
        {
            this.env = env;
            var lua = env.Current;
            lock (env)
            {
                lua.NewTable();
                for (int idx = 1; idx <= arr.Count; idx++)
                {
                    env.PushObjectToLuaStack(idx);
                    env.PushObjectToLuaStack(arr[idx] ?? UsableLuaType.nil);
                    lua.SetTable(-3);
                }
                ref_id = lua.Ref(LuaRegistry.Index);
            }
        }

        /// <summary>
        /// 所得为原表的浅克隆而非引用
        /// </summary>
        /// <param name="table"></param>
        public ExRefTable(IExETable table)
        {
            this.env = table.CurrentLua;
            var lua = env.Current;
            lock (env)
            {
                lua.NewTable();
                lua.PushNil();
                table.PushToLuaStack();
                while (true)
                {
                    lua.PushCopy(-2);
                    if (!lua.Next(-2))
                    {
                        break;
                    }
                    lua.Copy(-2, -4);
                    lua.SetTable(-5);
                }
                lua.Pop(2);
                this.ref_id = lua.Ref(LuaRegistry.Index);
            }
        }

        public static ExRefTable WatchFromLuaStack(EnvLua env, int index)
        {
            env.Current.PushCopy(index);
            return new ExRefTable(env, env.Current.Ref(LuaRegistry.Index));
        }

        public static ExRefTable FetchFromLuaStack(EnvLua env)
        {
            int ref_id = env.Current.Ref(LuaRegistry.Index);
            return new ExRefTable(env, ref_id);
        }

        public object Get(object key)
        {
            lock (env)
            {
                var lua = env.Current;
                lua.RawGetInteger(LuaRegistry.Index, RefId);
                env.PushObjectToLuaStack(key);
                lua.GetTable(-2);
                var value = env.FetchObjectFromLuaStack();
                lua.Pop(1);
                return value;
            }
        }

        public void Set(object key, object value)
        {
            lock (env)
            {
                var lua = env.Current;
                lua.RawGetInteger(LuaRegistry.Index, RefId);
                env.PushObjectToLuaStack(key);
                env.PushObjectToLuaStack(value);
                lua.SetTable(-3);
                lua.Pop(1);
            }
        }

        // public ExRefTable SetTable(object key)
        // {

        //     lua.RawGetInteger(LuaRegistry.Index, RefId);
        //     Tool.PushObjectToLuaStack(lua, key);
        //     lua.NewTable();
        //     lua.PushCopy(-1);
        //     var new_table_id = lua.Ref(LuaRegistry.Index);
        //     lua.SetTable(-3);
        //     return new ExRefTable(lua, new_table_id);
        // }

        public IDictionaryEnumerator GetEnumerator()
        {
            return new Enumerator(env, RefId);
        }

        public virtual void PushToLuaStack()
        {
            lock (env)
            {
                env.Current.RawGetInteger(LuaRegistry.Index, RefId);
            }
        }

        ~ExRefTable()
        {
            if (ref_id.HasValue)
            {
                lock (env)
                {
                    env.Current.Unref(LuaRegistry.Index, ref_id.Value);
                }
            }
        }

        public class Enumerator : IDictionaryEnumerator
        {
            private readonly EnvLua env;

            private int ref_id;

            private object? current_key = null;

            public object? Current { get; protected set; } = null;

            public DictionaryEntry Entry
            {
                get
                {
                    return new DictionaryEntry(current_key ?? UsableLuaType.nil, Current);
                }
            }

            public object Key
            {
                get
                {
                    return current_key ?? UsableLuaType.nil;
                }
            }

            public object? Value
            {
                get
                {
                    return Current;
                }
            }

            public Enumerator(EnvLua env, int ref_id)
            {
                this.env = env;
                this.ref_id = ref_id;
            }

            public bool MoveNext()
            {
                lock (env)
                {
                    bool result = false;
                    env.Current.RawGetInteger(LuaRegistry.Index, ref_id);
                    if (current_key != null)
                    {
                        env.PushObjectToLuaStack(current_key);
                    }
                    else
                    {
                        env.Current.PushNil();
                    }
                    if (env.Current.Next(-2))
                    {
                        Current = env.FetchObjectFromLuaStack();
                        current_key = env.FetchObjectFromLuaStack();
                        result = true;
                    }
                    else
                    {
                        Current = null;
                        result = false;
                    }
                    env.Current.Pop(-1);
                    return result;
                }
            }

            public void Reset()
            {
                current_key = null;
                Current = null;
            }
        }
    }
}